Web Avanzada Arduino/Esp8266
1. Introducción:
Anteriormente habíamos visto la forma de monitorizar o controlar un elemento desde una Web embebida pero haciendo
siempre un refresco de toda la página para actualizar la información. Esta forma no es la mejor manera e incluso puede ser
incómoda.
En este punto, trataremos de refrescar la información que nos interese manteniendo el resto; para esto se usa "JavaScript".
Estoy adentrándome en este mundo, y como principiante, trataré de ir aprendiendo y explicando.
2. Primer ejemplo AJAX:
En este ejemplo, se utiliza AJAX "Asynchronous JavaScript And XML". Al pulsar el botón, llamaremos a la función JavaScript,
que nos devolverá el estado de un pulsador en Arduino.
AJAX se usa para crear aplicaciones interactivas con el usuario. Un programa se ejeuta en el cliente (navegador) mientras se
mantiene una comunicación asíncrona con el servidor. Mediante este proceso, se pueden hacer cambios en la página sin necesidad
de recargarla completamente.
2.1. Código HTML de la página:
<!DOCTYPE html> <html> <head> <title>MicroEdu Web</title> <script> function GetSwitchState() { nocache = "&nocache=" + Math.random() * 1000000; var request = new XMLHttpRequest(); request.onreadystatechange = function() { if(this.readyState == 4) { if(this.status == 200) { if(this.responseText != null) { document.getElementById("switch_txt").innerHTML = this.responseText; } } } } request.open("GET", "ajax_switch" + nocache, true); request.send(null); } <script> </head> <body> <h1>Estado pulsador usando AJAX</h1> <p id="switch_txt">Estado pulsador: Desconocido ...</p> <button type="button" onclick="GetSwitchState()">Ver Estado Pulsador</button> </body> </html>
Cuando pulsamos sobre el botón "Ver Estado Pulsador", se llama a la función "GetSwitchState()". Esta función es un
JavaScript y por eso está entre etiquetas <script> ... </script>
Nada más entrar en la función, generamos un número aleatorio que asociaremos a la petición "GET". Este número se usa para que
cada petición sea diferente. Si no las diferenciásemos, el navegador mostraría el valor que tiene en la "caché" en vez de hacer
la solicitud:
"nocache = "&nocache=" + Math.random() * 1000000;"
Esta es la petición que se lanza:
"request.open("GET", "ajax_switch" + nocache, true);"
El siguente paso, es crear una variable llamada "request" dónde almacenaremos la respuesta que nos devuelve el servidor. En esta
respuesta tendremos unas propiedades que nos indican el estado:
- Propiedad ReadyState (Tiene el estado de la respuesta XMLHttpRequest)
- 0: Petición no inicializada
- 1: Conexión establecida con el servidor
- 2: Petición recibida
- 3: Procesando petición
- 4: Petición finalizada y respuesta lista
- Propiedad Status
- 200: "Ok"
- 403: "Forbidden"
- 404: "Page not found"
Si la respuesta ha sido realizada correctamente, busca en el cuerpo el elemento cuyo id corresponda a la etiqueta, y le cambia el valor por el texto recibido:
"document.getElementById("switch_txt").innerHTML = this.responseText;"
2.2. Código Arduino:
#include#include byte mac[] = {0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED}; //La mac que asignaremos al Wiznet IPAddress ip(192, 168, 168, 168); //La dirección asignada EthernetServer server(80); //Puerto de escucha del servidor String respuestaCliente; //String que almacenará la petición del cliente boolean estadoLed = false; //Variable que almacena el estado del Led void setup() { Serial.begin(9600); //Inicializamos la conexión Serie para ver lo que sucede Ethernet.begin(mac, ip); //Inicializamos la conexión Ethernet pinMode(7, INPUT); //Inicializamos el pin 7 como entrada server.begin(); //Levantamos el servidor escuchando en el puerto definido Serial.print("server is at "); //Por puerto Serie, nos indica qué IP tiene Serial.println(Ethernet.localIP()); } void loop() { EthernetClient client = server.available(); //Creamos un elemento client char c; //Variable para almacenar caracteres recibidos if (client) //Comprobamos si client ha recibido solicitud de conexión { Serial.println("Cliente nuevo"); boolean currentLineIsBlank = false; //Variable que nos indicará que hemos recibido línea nueva while (client.connected()) { while (client.available() > 0) //Recogemos todos los datos del buffer y los almacenamos en respuestaCliente { c = client.read(); respuestaCliente += c; } if (c == '\n' && currentLineIsBlank) { //Sacamos por puerto Serie lo recibido Serial.println(respuestaCliente); //Enviamos respuesta HTTP estándar client.println(F("HTTP/1.1 200 OK")); client.println(F("Content-Type: text/html")); client.println(F("Connection: close")); client.println(); //Si en la respuesta detectamos la palabra clave "ajax_switch", no enviamos la pagina //enviamos la respuesta a la consulta if (respuestaCliente.indexOf("ajax_switch") > -1) { procesar(client); } else { //Enviamos pagina client.println(F("<!DOCTYPE HTML>")); client.println(F("<html>")); client.println(F("<head>")); client.println(F("<title> MicroEdu Web</title>")); client.println(F("<script>")); client.println(F("function GetSwitchState(){nocache = '&nocache=' + Math.random() * 1000000;")); client.println(F("var request = new XMLHttpRequest();")); client.println(F("request.onreadystatechange = function(){")); client.println(F(" if(this.readyState == 4){")); client.println(F("if(this.status == 200){")); client.println(F("if(this.responseText != null){")); client.println(F("document.getElementById('switch_txt').innerHTML = this.responseText;}}}}")); client.println(F("request.open('GET', 'ajax_switch' + nocache, true);")); client.println(F("request.send(null);}")); client.println(F("</script>")); client.println(F("</head>")); client.println(F("<body> <h1> Estado Pulsador Usando AJAX </h1>")); client.println(F("<p id='switch_txt'>Estado pulsador: Desconocido ...</p>")); client.println(F("<button type='button' onclick='GetSwitchState()'>Ver Estado Pulsador</button>")); client.println(F("</body></html>")); client.println(); } respuestaCliente=""; break; } if (c == '\n') { currentLineIsBlank = true; //Nos preparamos para otra conexion Serial.println("Empezamos ..."); } else if (c != '\r') { currentLineIsBlank = false; //Aun no hemos terminado esta conexion Serial.println("No hemos terminado ..."); } client.flush(); } } delay(1); client.stop(); } void procesar(EthernetClient client2) { if (digitalRead(7)) { client2.println("Switch state: ON"); Serial.println ("Pulsador activado"); } else { client2.println("Switch state: OFF"); Serial.println ("Pulsador desactivado"); } }
2.3. Resultado:

3. Segundo ejemplo AJAX:
Este ejemplo es igual al anterior, pero se refresca el estado automáticamente sin necesidad de un botón.
3.1. Código HTML de la página:
<!DOCTYPE html> <html> <head> <title>MicroEdu Web</title> <script> function GetSwitchState() { nocache = "&nocache=" + Math.random() * 1000000; var request = new XMLHttpRequest(); request.onreadystatechange = function() { if(this.readyState == 4) { if(this.status == 200) { if(this.responseText != null) { document.getElementById("switch_txt").innerHTML = this.responseText; } } } } request.open("GET", "ajax_switch" + nocache, true); request.send(null); setTimeout('GetSwitchState()', 1000); } <script> </head> <body onload="GetSwitchState()"> <h1>Estado pulsador usando AJAX</h1> <p id="switch_txt">Estado pulsador: Desconocido ...</p> </body> </html>
En la función script, añadimos un línea que indica que debe ejecutarse cada segundo esta función:
"setTimeout('GetSwitchState()', 1000);"
Luego, le pasamos el parámetro "onload" al cuerpo de la página. Al hacer esto, una vez que termina de cargarse
la página, llama a la función indicada:
"<body onload="GetSwitchState()">"
Nota: Parece que cada vez que actualiza el texto, identifica como que
termina de cargar y vuelve a llamar a la función. Hace esto continuamente; por eso se refresca cada segundo.
3.2. Código Arduino:
#include#include byte mac[] = {0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED}; //La mac que asignaremos al Wiznet IPAddress ip(192, 168, 168, 168); //La dirección asignada EthernetServer server(80); //Puerto de escucha del servidor String respuestaCliente; //String que almacenará la petición del cliente boolean estadoLed = false; //Variable que almacena el estado del Led void setup() { Serial.begin(9600); //Inicializamos la conexión Serie para ver lo que sucede Ethernet.begin(mac, ip); //Inicializamos la conexión Ethernet pinMode(7, INPUT); //Inicializamos el pin 7 como entrada server.begin(); //Levantamos el servidor escuchando en el puerto definido Serial.print("server is at "); //Por puerto Serie, nos indica qué IP tiene Serial.println(Ethernet.localIP()); } void loop() { EthernetClient client = server.available(); //Creamos un elemento client char c; //Variable para almacenar caracteres recibidos if (client) //Comprobamos si client ha recibido solicitud de conexión { Serial.println("Cliente nuevo"); boolean currentLineIsBlank = false; //Variable que nos indicará que hemos recibido línea nueva while (client.connected()) { while (client.available() > 0) //Recogemos todos los datos del buffer y los almacenamos en respuestaCliente { c = client.read(); respuestaCliente += c; } if (c == '\n' && currentLineIsBlank) { //Sacamos por puerto Serie lo recibido Serial.println(respuestaCliente); //Enviamos respuesta HTTP estándar client.println(F("HTTP/1.1 200 OK")); client.println(F("Content-Type: text/html")); client.println(F("Connection: close")); client.println(); //Si en la respuesta detectamos la palabra clave "ajax_switch", no enviamos la pagina //enviamos la respuesta a la consulta if (respuestaCliente.indexOf("ajax_switch") > -1) { procesar(client); } else { //Enviamos pagina client.println(F("<!DOCTYPE HTML>")); client.println(F("<html>")); client.println(F("<head>")); client.println(F("<title> MicroEdu Web</title>")); client.println(F("<script>")); client.println(F("function GetSwitchState(){nocache = '&nocache=' + Math.random() * 1000000;")); client.println(F("var request = new XMLHttpRequest();")); client.println(F("request.onreadystatechange = function(){")); client.println(F(" if(this.readyState == 4){")); client.println(F("if(this.status == 200){")); client.println(F("if(this.responseText != null){")); client.println(F("document.getElementById('switch_txt').innerHTML = this.responseText;}}}}")); client.println(F("request.open('GET', 'ajax_switch' + nocache, true);")); client.println(F("request.send(null);")); client.println(F("setTimeout('GetSwitchState()', 1000);}")); client.println(F("</script>")); client.println(F("</head>")); client.println(F("<body onload='GetSwitchState()'> <h1> Estado Pulsador Usando AJAX </h1>")); client.println(F("<p id='switch_txt'>Estado pulsador: Desconocido ...</p>")); client.println(F("</body></html>")); client.println(); } respuestaCliente=""; break; } if (c == '\n') { currentLineIsBlank = true; //Nos preparamos para otra conexion Serial.println("Empezamos ..."); } else if (c != '\r') { currentLineIsBlank = false; //Aun no hemos terminado esta conexion Serial.println("No hemos terminado ..."); } client.flush(); } } delay(1); client.stop(); } void procesar(EthernetClient client2) { if (digitalRead(7)) { client2.println("Switch state: ON"); Serial.println ("Pulsador activado"); } else { client2.println("Switch state: OFF"); Serial.println ("Pulsador desactivado"); } }
3.3. Resultado:

4. Empezando con XML:
En este ejemplo, el Arduino devuelve el estado de las entradas usando un fichero o estructura XML y no un bloque de HTML.
El XML se usa para representar información estructurada en la Web y esto facilita que los valores sean fácilmente extraíbles
por JavaScript sin necesidad de añadir código para interpretarlos.
Tiene la siguiente estructura:

Los nombres de las variables pueden ser repetidos o diferentes. Si son repetidos, se accede mediante un índice:

En el siguiente ejemplo usa nombres diferentes:

En este caso se acceden mediante el nombre e índice 0:

En el siguiente ejemplo, se pasará una estructura XML, de la que se extraerá el valor de la variable que se necesite.
4.1. Código HTML:
<!DOCTYPE html> <html> <head> <title>MicroEdu Web</title> <script> function GetSwitchState() { nocache = "&nocache=" + Math.random() * 1000000; var request = new XMLHttpRequest(); request.onreadystatechange = function() { if(this.readyState == 4) { if(this.status == 200) { if(this.responseText != null) { document.getElementById("contador_1").innerHTML = this.responseXML.getElementsByTagName('cont_1')[0].chiNodes[0].nodeValue; document.getElementById("contador_2").innerHTML = this.responseXML.getElementsByTagName('cont_2')[0].chiNodes[0].nodeValue; document.getElementById("pulsador_1").innerHTML = this.responseXML.getElementsByTagName('puls_1')[0].chiNodes[0].nodeValue; } } } } request.open("GET", "ajax_switch" + nocache, true); request.send(null); setTimeout('GetArduinoInputs()', 1000); } </script> </head> <body onload="GetArduinoInputs()"> <h1>Estado contadores y pulsador usando AJAX y XML</h1> <p>Estado Pulsador: <span id="pulsador_1">...</span></p> <p>Valor Contador 1: <span id="contador_1">...</span></p> <p>Valor Contador 2: <span id="contador_2">...</span></p> </body> </html>
El código en Arduino:
#include#include byte mac[] = {0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED}; //La mac que asignaremos al Wiznet IPAddress ip(192, 168, 168, 168); //La dirección asignada EthernetServer server(80); //Puerto de escucha del servidor String respuestaCliente; //String que almacenará la petición del cliente boolean estadoLed = false; //Variable que almacena el estado del Led int contador_1, contador_2; //Variables de contador void setup() { Serial.begin(9600); //Inicializamos la conexión Serie para ver lo que sucede Ethernet.begin(mac, ip); //Inicializamos la conexión Ethernet pinMode(7, INPUT); //Inicializamos el pin 7 como entrada server.begin(); //Levantamos el servidor escuchando en el puerto definido Serial.print("server is at "); //Por puerto Serie, nos indica qué IP tiene Serial.println(Ethernet.localIP()); } void loop() { EthernetClient client = server.available(); //Creamos un elemento client char c; //Variable para almacenar caracteres recibidos if (client) //Comprobamos si client ha recibido solicitud de conexión { Serial.println("Cliente nuevo"); boolean currentLineIsBlank = false; //Variable que nos indicará que hemos recibido línea nueva while (client.connected()) { while (client.available() > 0) //Recogemos todos los datos del buffer y los almacenamos en respuestaCliente { c = client.read(); respuestaCliente += c; } if (c == '\n' && currentLineIsBlank) { //Sacamos por puerto Serie lo recibido Serial.println(respuestaCliente); //Cabecera estandar de respuesta client.println(F("HTTP/1.1 200 OK")); //Si en la respuesta detectamos la palabra clave "ajax_switch", no enviamos la pagina //enviamos la respuesta a la consulta if (respuestaCliente.indexOf("xml_values") > -1) { //Enviamos respuesta XML estándar client.println(F("Content-Type: text/xml")); client.println(F("Connection: keep-alive")); client.println(); procesar(client); } else { //Enviamos respuesta HTTP estándar client.println(F("Content-Type: text/html")); client.println(F("Connection: keep-alive")); client.println(); //Enviamos pagina client.println(F("<!DOCTYPE HTML>")); client.println(F("<html>")); client.println(F("<head>")); client.println(F("<title> MicroEdu Web</title>")); client.println(F("<script>")); client.println(F("function GetArduinoValues(){nocache = '&nocache=' + Math.random() * 1000000;")); client.println(F("var request = new XMLHttpRequest();")); client.println(F("request.onreadystatechange = function(){")); client.println(F(" if(this.readyState == 4){")); client.println(F("if(this.status == 200){")); client.println(F("if(this.responseText != null){")); //client.println(F("document.getElementById('contador_1').innerHTML = this.responseXML.getElementsByTagName('cont1')[0].childNodes[0].nodeValue;}}}}")); client.println(F("document.getElementById('contador1').innerHTML = this.responseXML.getElementsByTagName('cont1')[0].childNodes[0].nodeValue;")); client.println(F("document.getElementById('contador2').innerHTML = this.responseXML.getElementsByTagName('cont2')[0].childNodes[0].nodeValue;")); client.println(F("document.getElementById('pulsador1').innerHTML = this.responseXML.getElementsByTagName('puls1')[0].childNodes[0].nodeValue;}}}}")); client.println(F("request.open('GET', 'xml_values' + nocache, true);")); client.println(F("request.send(null);")); client.println(F("setTimeout('GetArduinoValues()', 1000);}")); client.println(F("</script>")); client.println(F("</head>")); client.println(F("<body onload = 'GetArduinoValues()'> <h1> Estado contadores y pulsador usando AJAX y XML </h1>")); client.println(F("<p>Estado Pulsador: <span id='pulsador1'>...</span></p>")); client.println(F("<p>Estado Contador 1: <span id='contador1'>...</span></p>")); client.println(F("<p>Estado Contador 2: <span id='contador2'>...</span></p>")); client.println(F("</body></html>")); client.println(); } respuestaCliente=""; break; } if (c == '\n') { currentLineIsBlank = true; //Nos preparamos para otra conexion Serial.println("Empezamos ..."); } else if (c != '\r') { currentLineIsBlank = false; //Aun no hemos terminado esta conexion Serial.println("No hemos terminado ..."); } client.flush(); } } delay(1); client.stop(); } void procesar(EthernetClient client2) { client2.println(""); client2.println(" "); client2.print(" "); }"); if (digitalRead(7)) { client2.print("Switch state: ON"); Serial.print ("Pulsador activado"); } else { client2.print("Switch state: OFF"); Serial.print ("Pulsador desactivado"); } client2.println(" "); client2.print(""); if (contador_1 < 200) contador_1++; else contador_1 = 0; client2.print (contador_1); client2.println(" "); client2.print(""); if (contador_2 < 200) contador_2 = contador_2 + 5; else contador_2 = 0; client2.print(contador_2); client2.println(" "); client2.println("
El resultado:

5. Descargas: